﻿using SharpDX.Direct3D11;
using System.Windows.Forms;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Windows;
using Device = SharpDX.Direct3D11.Device;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using D2D = SharpDX.Direct2D1;
using System.Drawing;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
//using System;
using ModelViewer;


namespace Engine
{
    public static class DrawHelper
    {
        static Viewport OcclViewport;
        static Texture2D[] OcclTexture;
        static ShaderResourceView[] OcclSRV;
        static RenderTargetView[] OcclRTV;

        static EffectContainer EEEM;
        static MeshContainer MConteints;

        static ShaderConstants constants;
        static Buffer constantsBuffer;

        public static SamplerState SamplLinWrap;
        public static SamplerState SamplPntWrap;
        public static SamplerState SamplAnsWrap;
        public static SamplerState SamplLinWrapMip;
        public static SamplerState SamplPntWrapMip;
        public static SamplerState SamplAnsWrapMip;

        public const int OcclPixelInTex = 1024;

        private static DVector3[] OcclusionRequestPosition = new DVector3[OcclPixelInTex];
        private static int LastFreeIndex = 0;

        public static void Init(Device device)
        {
            #region Sampler
            SamplerStateDescription desc = new SamplerStateDescription();
            desc.AddressU = TextureAddressMode.Wrap;
            desc.AddressV = TextureAddressMode.Wrap;
            desc.AddressW = TextureAddressMode.Wrap;
            desc.Filter = Filter.MinMagMipLinear;
            SamplLinWrap = new SamplerState(ModelViewer.Program.device, desc);
            desc.Filter = Filter.MinMagMipPoint;
            SamplPntWrap = new SamplerState(ModelViewer.Program.device, desc);
            desc.Filter = Filter.Anisotropic;
            if (Global.Settings.TextureFiltering > -1) desc.MaximumAnisotropy = System.Math.Min(Global.Settings.TextureFiltering, 16);
            else desc.MaximumAnisotropy = 0;
            SamplAnsWrap = new SamplerState(ModelViewer.Program.device, desc);
            desc.MaximumAnisotropy = 0;
            desc.MinimumLod = 0;
            desc.MaximumLod = 7;
            desc.Filter = Filter.MinMagMipLinear;
            SamplLinWrapMip = new SamplerState(ModelViewer.Program.device, desc);
            desc.Filter = Filter.MinMagMipPoint;
            SamplPntWrapMip = new SamplerState(ModelViewer.Program.device, desc);
            desc.Filter = Filter.Anisotropic;
            if (Global.Settings.TextureFiltering > -1) desc.MaximumAnisotropy = System.Math.Min(Global.Settings.TextureFiltering, 16);
            else desc.MaximumAnisotropy = 0;
            SamplAnsWrapMip = new SamplerState(ModelViewer.Program.device, desc);
            #endregion

            OcclViewport = new Viewport(0, 0, OcclPixelInTex, 1);

            OcclTexture = new Texture2D[1];
            OcclSRV = new ShaderResourceView[1];
            OcclRTV = new RenderTargetView[1];
            InitOcclInd(0);

            MConteints = new MeshContainer();

            MConteints.BytesPerVertex = 12;
            MConteints.FaceCount = 2;
            MConteints.VertexsCount = 4;

            var vertices = new DataStream(MConteints.BytesPerVertex * MConteints.VertexsCount, true, true);
            vertices.Write(new Vector3(0.0f, 0.0f, 0.5f));
            vertices.Write(new Vector3(1.0f, 0.0f, 0.5f));
            vertices.Write(new Vector3(1.0f, 1.0f, 0.5f));
            vertices.Write(new Vector3(0.0f, 1.0f, 0.5f));
            vertices.Position = 0;

            MConteints.Vertexs = new Buffer(device, vertices, MConteints.BytesPerVertex * MConteints.VertexsCount, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);

            var indices = new DataStream(4 * MConteints.FaceCount * 3, true, true);
            indices.Write(0);
            indices.Write(1);
            indices.Write(2);
            indices.Write(2);
            indices.Write(3);
            indices.Write(0);
            indices.Position = 0;

            MConteints.Indices = new Buffer(device, indices, 4 * MConteints.FaceCount * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            MConteints.binding = new VertexBufferBinding(MConteints.Vertexs, MConteints.BytesPerVertex, 0);

            InputElement[] elements11 = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0) };
            EEEM = ContentManager.LoadEffect("Content/Shaders/OcclusionTest", elements11);

            // Подготовка константного буффера
            BufferDescription bd = new BufferDescription();
            bd.SizeInBytes = Marshal.SizeOf(typeof(ShaderConstants));
            bd.Usage = ResourceUsage.Dynamic;
            bd.BindFlags = BindFlags.ConstantBuffer;
            bd.CpuAccessFlags = CpuAccessFlags.Write;
            bd.OptionFlags = ResourceOptionFlags.None;
            bd.StructureByteStride = 0;

            constantsBuffer = new Buffer(device, bd);
            constants = new ShaderConstants();

            constants.Resolution = new Vector4(Global.Settings.Width, Global.Settings.Height, OcclPixelInTex, 1);
            constants.WM = Matrix.Transpose(Matrix.OrthoLH(constants.Resolution.X, constants.Resolution.Y, 0.1f, 1000));

            LensBatching.Init(device);
            PSSMsHelper.Init(device);
        }

        private static void InitOcclInd(int Ind)
        {
            Texture2DDescription TexturDescription = new Texture2DDescription();
            TexturDescription.MipLevels = 1;
            TexturDescription.ArraySize = 1;
            TexturDescription.Format = Format.R8G8B8A8_UNorm;
            TexturDescription.SampleDescription = new SampleDescription(1, 0);
            TexturDescription.Usage = ResourceUsage.Default;
            TexturDescription.BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource;
            TexturDescription.CpuAccessFlags = CpuAccessFlags.None;
            TexturDescription.OptionFlags = ResourceOptionFlags.None;
            TexturDescription.Height = 1;
            TexturDescription.Width = OcclPixelInTex;

            OcclTexture[Ind] = new Texture2D(ModelViewer.Program.device, TexturDescription);
            OcclSRV[Ind] = new ShaderResourceView(ModelViewer.Program.device, OcclTexture[Ind]);

            RenderTargetViewDescription RTVD = new RenderTargetViewDescription();
            RTVD.Format = TexturDescription.Format;
            RTVD.Dimension = RenderTargetViewDimension.Texture2D;
            RTVD.Texture2DArray.FirstArraySlice = 0;
            RTVD.Texture2DArray.ArraySize = 1;
            RTVD.Texture2D.MipSlice = 0;

            OcclRTV[Ind] = new RenderTargetView(ModelViewer.Program.device, OcclTexture[Ind], RTVD);

        }

        public static void FreeResource()
        {
            SamplAnsWrap.Dispose();
            SamplAnsWrapMip.Dispose();
            SamplLinWrap.Dispose();
            SamplLinWrapMip.Dispose();
            SamplPntWrap.Dispose();
            SamplPntWrapMip.Dispose();

            if (OcclTexture != null)
                for (int i = 0; i < OcclTexture.Length; i++)
                    OcclTexture[i].Dispose();

            if (OcclSRV != null)
                for (int i = 0; i < OcclSRV.Length; i++)
                    OcclSRV[i].Dispose();

            if (OcclRTV != null)
                for (int i = 0; i < OcclRTV.Length; i++)
                    OcclRTV[i].Dispose();

            LensBatching.FreeResource();
            PSSMsHelper.FreeResource();
        }

        public static int OcclusionRequest(DVector3 Position)
        {
            if (LastFreeIndex >= OcclusionRequestPosition.Length)
                System.Array.Resize(ref OcclusionRequestPosition, OcclusionRequestPosition.Length * 2);

            OcclusionRequestPosition[LastFreeIndex] = Position;
            LastFreeIndex++;

            if (LastFreeIndex > OcclSRV.Length * OcclPixelInTex)
            {
                //Resize
                int newsize = OcclSRV.Length * 2;
                System.Array.Resize(ref OcclTexture, newsize);
                System.Array.Resize(ref OcclSRV, newsize);
                System.Array.Resize(ref OcclRTV, newsize);

                for (int i = 0; i < newsize; i++)
                    if (OcclTexture[i] == null)
                        InitOcclInd(i);
            }
            return LastFreeIndex - 1;
        }

        public static void GetOcclusionTestResult(int RequestInt, out ShaderResourceView SRV, out int Index)
        {
            SRV = OcclSRV[RequestInt / OcclPixelInTex];
            Index = RequestInt % OcclPixelInTex;
        }

        public static void OcclusionTest(Device device, EEDCamera Cam, ShaderResourceView DepthMap)
        {
            device.ImmediateContext.InputAssembler.InputLayout = EEEM.layout;
            device.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            device.ImmediateContext.InputAssembler.SetVertexBuffers(0, MConteints.binding);
            device.ImmediateContext.InputAssembler.SetIndexBuffer(MConteints.Indices, Format.R32_UInt, 0);

            device.ImmediateContext.Rasterizer.SetViewports(OcclViewport);

            int stepcount = LastFreeIndex / OcclPixelInTex;
            for (int i = 0; i <= stepcount; i++)
            {
                int TestCount = OcclPixelInTex;
                if (i == stepcount)
                    TestCount = LastFreeIndex % OcclPixelInTex;

                device.ImmediateContext.OutputMerger.SetTargets(OcclRTV[i]);
                device.ImmediateContext.ClearRenderTargetView(OcclRTV[i], new Color4(0.0f, 0.0f, 0.0f, 1.0f));

                EEEM.renderTechniques[0].GetPassByIndex(0).Apply(device.ImmediateContext);

                device.ImmediateContext.PixelShader.SetShaderResource(0, DepthMap);
                device.ImmediateContext.PixelShader.SetSampler(0, SamplPntWrap);

                //
                //constants
                constants.CamViewProj = Matrix.Transpose(Cam.viewProjectionMatrix);
                constants.FarPlane = new Vector4(Cam.farClip, 0, 0, 0);
                unsafe
                {
                    fixed (float* buffer = constants.Positions)
                    {
                        for (int x = 0; x < TestCount; x++)
                        {
                            DVector3 Pos = OcclusionRequestPosition[x + i * OcclPixelInTex];
                            Vector3 LocPos = Conversion.ToVector3(Pos - Cam.position);

                            buffer[x * 4 + 0] = LocPos.X;
                            buffer[x * 4 + 1] = LocPos.Y;
                            buffer[x * 4 + 2] = LocPos.Z;
                            buffer[x * 4 + 3] = 0;
                        }
                    }
                }

                var dataBox = ModelViewer.Program.context.MapSubresource(constantsBuffer, 0, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None);
                Utilities.Write(dataBox.DataPointer, ref constants);
                device.ImmediateContext.UnmapSubresource(constantsBuffer, 0);

                //Константный буффер передается шейдерам
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);

                device.ImmediateContext.DrawIndexed(6, 0, 0);
            }
            LastFreeIndex = 0;
        }

        private unsafe struct ShaderConstants
        {
            public Matrix WM;
            public Matrix CamViewProj;
            public Vector4 Resolution;//scr xy, OcclPixelInTex,1
            public Vector4 FarPlane;
            public fixed float Positions[OcclPixelInTex * 4];
        }
    }

    public static class PSSMsHelper
    {
        public const int SplitsNumber = 6;
        public static double ShadowDistance = 5000;
        public static int SplitShadowsSize = 2048;

        public static Matrix[] CropMatrices = new Matrix[SplitsNumber];
        public static DVector3[] NewCamPositions = new DVector3[SplitsNumber];
        static double[] SplitSlices = new double[SplitsNumber + 1];
        static DVector3[,] CamFrustumPos = new DVector3[SplitsNumber + 1, 4];
        public static Matrix LightViewProjMatrix;
        static List<GameObject>[] ObjInSplites = new List<GameObject>[SplitsNumber];
        public static DVector3 NewShadowPos;

        static Texture2D SMArrayTex;
        public static ShaderResourceView SMArraySRV;
        static DepthStencilView SMArrayDSV;
        static Viewport SMArrayV;

        public static void Init(Device device)
        {
            //Max
            if (Global.Settings.ShadowQuality == 0)
            {
                SplitShadowsSize = 3072;
                ShadowDistance *= 1f;
            }
            //Avg
            if (Global.Settings.ShadowQuality == 1)
            {
                SplitShadowsSize = 2048;
                ShadowDistance *= 0.75f;
            }
            //Min
            if (Global.Settings.ShadowQuality == 2)
            {
                SplitShadowsSize = 1536;
                ShadowDistance *= 0.5f;
            }

            Texture2DDescription DepthStencilDescription = new Texture2DDescription();
            DepthStencilDescription.Height = SplitShadowsSize;
            DepthStencilDescription.Width = SplitShadowsSize;
            DepthStencilDescription.ArraySize = SplitsNumber;
            DepthStencilDescription.Format = Format.R32_Typeless;
            DepthStencilDescription.Usage = ResourceUsage.Default;
            DepthStencilDescription.BindFlags = BindFlags.DepthStencil | BindFlags.ShaderResource;//BindFlags.RenderTarget
            DepthStencilDescription.MipLevels = 1;
            DepthStencilDescription.SampleDescription = new SampleDescription(1, 0);
            DepthStencilDescription.CpuAccessFlags = CpuAccessFlags.None;
            DepthStencilDescription.OptionFlags = ResourceOptionFlags.None;

            SMArrayTex = new Texture2D(ModelViewer.Program.device, DepthStencilDescription);

            ShaderResourceViewDescription SRVD = new ShaderResourceViewDescription();
            SRVD.Format = Format.R32_Float;
            SRVD.Dimension = ShaderResourceViewDimension.Texture2DArray;
            SRVD.Texture2DArray.ArraySize = SplitsNumber;
            SRVD.Texture2DArray.MipLevels = 1;
            SRVD.Texture2DArray.MostDetailedMip = 0;
            SMArraySRV = new ShaderResourceView(device, SMArrayTex, SRVD);

            DepthStencilViewDescription DSVD = new DepthStencilViewDescription();
            DSVD.Dimension = DepthStencilViewDimension.Texture2DArray;
            DSVD.Format = Format.D32_Float;
            DSVD.Texture2DArray.ArraySize = SplitsNumber;
            SMArrayDSV = new DepthStencilView(ModelViewer.Program.device, SMArrayTex, DSVD);

            SMArrayV = new Viewport(0, 0, SplitShadowsSize, SplitShadowsSize);

        }

        public static void FreeResource()
        {
            SMArrayTex.Dispose();
            SMArraySRV.Dispose();
            SMArrayDSV.Dispose();
        }

        private static void GenerateSplit(EEDCamera Camera, DVector3 ShadowVector)
        {
            Vector3 Up = new Vector3(0, 1, 0);
            NewShadowPos = Camera.position;
            DVector3.Normalize(ShadowVector);
            float d = Vector3.Dot(ShadowVector, Up);
            if (d == 1 || d == -1)
            {
                Up = new Vector3(1, 0, 0);
            }
            NewShadowPos -= ShadowDistance * ShadowVector;

            const int ShadowStep = 50;
            NewShadowPos.X = ((int)NewShadowPos.X) / ShadowStep * ShadowStep;
            NewShadowPos.Y = ((int)NewShadowPos.Y) / ShadowStep * ShadowStep;
            NewShadowPos.Z = ((int)NewShadowPos.Z) / ShadowStep * ShadowStep;

            double Coff = 0.125f;
            for (int i = 0; i < SplitsNumber + 1; i++)
            {
                SplitSlices[i] = (Camera.nearClip + (Camera.farClip - Camera.nearClip) * (float)i / (float)SplitsNumber) * (Coff);
                SplitSlices[i] += (Camera.nearClip * System.Math.Pow(Camera.farClip / Camera.nearClip, (float)i / (float)SplitsNumber)) * (1 - Coff);
                SplitSlices[i] /= Camera.farClip;
                SplitSlices[i] *= ShadowDistance;

                CamFrustumPos[i, 0] = Camera.position + (Camera.FarT1 - Camera.position) * (SplitSlices[i] / Camera.farClip);
                CamFrustumPos[i, 1] = Camera.position + (Camera.FarT2 - Camera.position) * (SplitSlices[i] / Camera.farClip);
                CamFrustumPos[i, 2] = Camera.position + (Camera.FarT3 - Camera.position) * (SplitSlices[i] / Camera.farClip);
                CamFrustumPos[i, 3] = Camera.position + (Camera.FarT4 - Camera.position) * (SplitSlices[i] / Camera.farClip);
            }

            Matrix ProjShadowMatrix = Matrix.OrthoRH(100f, 100f, (float)ShadowDistance / 1000f, (float)ShadowDistance * 2f);
            Matrix ViewShadowMatrix = Matrix.LookAtRH(new Vector3(0, 0, 0), Conversion.ToVector3(ShadowVector), Vector3.Cross(Camera.direction, ShadowVector));
            LightViewProjMatrix = ViewShadowMatrix * ProjShadowMatrix;

            for (int i = 0; i < SplitsNumber; i++)
            {

                Vector3[] TransformSplit = new Vector3[8];
                for (int t = 0; t < 4; t++)
                {
                    TransformSplit[t] = Conversion.ToVector3(Vector3.Transform(Conversion.ToVector3(CamFrustumPos[i, t] - NewShadowPos), LightViewProjMatrix));
                    TransformSplit[t + 4] = Conversion.ToVector3(Vector3.Transform(Conversion.ToVector3(CamFrustumPos[i + 1, t] - NewShadowPos), LightViewProjMatrix));
                }

                Vector3 AABBMax = TransformSplit[0];
                Vector3 AABBMin = TransformSplit[0];

                for (int y = 0; y < 8; y++)
                {
                    if (AABBMax.X < TransformSplit[y].X) AABBMax.X = TransformSplit[y].X;
                    if (AABBMax.Y < TransformSplit[y].Y) AABBMax.Y = TransformSplit[y].Y;
                    if (AABBMax.Z < TransformSplit[y].Z) AABBMax.Z = TransformSplit[y].Z;

                    if (AABBMin.X > TransformSplit[y].X) AABBMin.X = TransformSplit[y].X;
                    if (AABBMin.Y > TransformSplit[y].Y) AABBMin.Y = TransformSplit[y].Y;
                    if (AABBMin.Z > TransformSplit[y].Z) AABBMin.Z = TransformSplit[y].Z;
                }
                /*
                AABBMax += new Vector3(0.05f);
                AABBMin -= new Vector3(0.05f);
                */

                // Create the crop matrix  
                float scaleX, scaleY, scaleZ;
                float offsetX, offsetY, offsetZ;
                // Use default near-plane value  
                AABBMin.Z = 0.0f;

                scaleX = 2.0f / (AABBMax.X - AABBMin.X);
                scaleY = 2.0f / (AABBMax.Y - AABBMin.Y);
                offsetX = -0.5f * (AABBMax.X + AABBMin.X) * scaleX;
                offsetY = -0.5f * (AABBMax.Y + AABBMin.Y) * scaleY;
                scaleZ = 1.0f / ((AABBMax.Z - AABBMin.Z));
                offsetZ = -AABBMin.Z * scaleZ;


                const int CoffShadowStep = 5;
                const float CoffMulti = 100f;
                const float CoffMultiScale = 1000f;

                offsetX = ((float)(((int)(offsetX * CoffMulti)) / CoffShadowStep * CoffShadowStep)) / CoffMulti;
                offsetY = ((float)(((int)(offsetY * CoffMulti)) / CoffShadowStep * CoffShadowStep)) / CoffMulti;
                offsetZ = ((float)(((int)(offsetZ * CoffMulti)) / CoffShadowStep * CoffShadowStep)) / CoffMulti;

                scaleX = ((float)(((int)(scaleX * CoffMultiScale)) / CoffShadowStep * CoffShadowStep)) / CoffMultiScale;
                scaleY = ((float)(((int)(scaleY * CoffMultiScale)) / CoffShadowStep * CoffShadowStep)) / CoffMultiScale;
                scaleZ = ((float)(((int)(scaleZ * CoffMultiScale)) / CoffShadowStep * CoffShadowStep)) / CoffMultiScale;


                CropMatrices[i] = new Matrix(scaleX, 0.0f, 0.0f, 0.0f, 0.0f, scaleY, 0.0f, 0.0f, 0.0f, 0.0f, scaleZ, 0.0f, offsetX, offsetY, offsetZ, 1.0f);

                DMatrix Inv = DMatrix.Invert(Conversion.ToDoubleMatrix(LightViewProjMatrix * CropMatrices[i]));

                DVector3 LocPos = DVector3.Transform(new DVector3(), Inv);
                NewCamPositions[i] = NewShadowPos + LocPos;
            }
        }

        public static void RenderAllSplits(Device device, EEDCamera Camera, DVector3 ShadowVector, KDtree KDT, List<GameObject> ObjInCam)
        {
            for (int t = 0; t < ObjInCam.Count; t++)
                if (ObjInCam[t] != null)
                {
                    ObjInCam[t].StartSplit = -1;
                    ObjInCam[t].StopSplit = -1;
                }
            //string title = "";
            GenerateSplit(Camera, ShadowVector);
            for (int i = 0; i < SplitsNumber; i++)
            {
                DMatrix Inv = DMatrix.Invert(Conversion.ToDoubleMatrix(LightViewProjMatrix * CropMatrices[i]));
                Vector3 View = Conversion.ToVector3(DVector3.Transform(new DVector3(0, 0, 1), Inv) - DVector3.Transform(new DVector3(0, 0, 0), Inv));
                Vector3 Right = Conversion.ToVector3(DVector3.Transform(new DVector3(1, 0, 0), Inv) - DVector3.Transform(new DVector3(0, 0, 0), Inv));
                Vector3 Up = Conversion.ToVector3(DVector3.Transform(new DVector3(0, 1, 0), Inv) - DVector3.Transform(new DVector3(0, 0, 0), Inv));
                BoundingFrustum BF = new BoundingFrustum(View, Right, Up);

                ObjInSplites[i] =

                    //KDT.GetBody(new DVector3(), ShadowDistance);
                    KDT.GetBodyInCamera(NewCamPositions[i], BF);

                //title += "  "  + ObjInSplites[i].Count;
            }
            //Program.form.Text = title;
            RenderTargetView RTV_ = null;
            device.ImmediateContext.OutputMerger.SetTargets(SMArrayDSV, RTV_);

            device.ImmediateContext.ClearDepthStencilView(SMArrayDSV, DepthStencilClearFlags.Depth, 1.0f, 0);

            device.ImmediateContext.Rasterizer.SetViewports(SMArrayV);

            for (int i = 0; i < SplitsNumber; i++)
            {
                for (int y = 0; y < ObjInSplites[i].Count; y++)
                {
                    GameObject GO = ObjInSplites[i][y];
                    if (GO != null)
                    {
                        int Start = i;
                        int Stop = i;

                        if (i < SplitsNumber)
                        {
                            for (int j = i + 1; j < SplitsNumber; j++)
                            {
                                //Перебор последующих срезов
                                if (ObjInSplites[j].Contains(GO))
                                {
                                    Stop = j;
                                    ObjInSplites[j].Remove(GO);
                                }
                            }
                        }
                        //Draw
                        GO.StartSplit = Start;
                        GO.StopSplit = Stop;

                        GO.DrawPSSMSplit(device, NewShadowPos, LightViewProjMatrix, CropMatrices, Start, Stop);
                    }
                }
            }
        }

        public static void RenderObj(float TimeMili, Device device, EEDCamera Camera, Vector3 LightDirection, Vector3 LightColor, List<GameObject> GOs)
        {
            Matrix[] S_VP = new Matrix[SplitsNumber];
            float[] SplitDistances = new float[SplitsNumber];
            for (int i = 0; i < SplitsNumber; i++)
            {
                S_VP[i] = LightViewProjMatrix * CropMatrices[i];
                SplitDistances[i] = (float)SplitSlices[i + 1];///Camera.farClip;
            }
            DMatrix W_S_CAM = DMatrix.CreateTranslation(-NewShadowPos);

            Matrix[] S_WVP = new Matrix[SplitsNumber];
            for (int i = 0; i < GOs.Count; i++)
                if (GOs[i] != null)
                {
                    for (int y = 0; y < SplitsNumber; y++)
                    {
                        S_WVP[y] = Conversion.ToMatrix(GOs[i].Transformation * DMatrix.CreateTranslation(-NewShadowPos)) * S_VP[y];
                    }

                    if (GOs[i].StopSplit > -1)
                        GOs[i].DrawPSSM(device, TimeMili, Camera, LightDirection, LightColor, S_WVP, SplitDistances);
                    else
                        GOs[i].Draw(device, Camera, LightColor, LightDirection);
                }
        }
    }

    public static class LensBatching
    {
        static Buffer Vertexs;
        static VertexBufferBinding binding;

        const int BytesPerVertex = 32;
        static int LensCount = 0;

        static ShaderConstants constants;
        static Buffer constantsBuffer;

        static EffectContainer EEEM;

        static int MassSize = 128;
        static Request[] MasterRequest;
        static Request[] SlaveRequest;

        static int LastFreeIndex = 0;
        static bool ResizeVertex = false;

        public static void Init(Device device)
        {
            MasterRequest = new Request[128];
            SlaveRequest = new Request[128];
            for (int i = 0; i < MasterRequest.Length; i++)
            {
                MasterRequest[i] = new Request();
                SlaveRequest[i] = new Request();
            }

            //float3 Position //12
            //half2 Size //16
            //half4 AABBTexCoord //24
            //half4 AditiveColor //32
            InputElement[] elements = new[] {
                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0),
                new InputElement("TEXCOORD", 0, Format.R16G16_Float, 12, 0),
                new InputElement("TEXCOORD", 1, Format.R16G16B16A16_Float, 16, 0),
                new InputElement("TEXCOORD", 2, Format.R16G16B16A16_Float, 24, 0),
            };

            Vertexs = new Buffer(device, BytesPerVertex * MassSize, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
            //binding = new VertexBufferBinding(Vertexs, BytesPerVertex, 0);

            EEEM = ContentManager.LoadEffect("Content/Shaders/GSSprite", elements);

            // Подготовка константного буффера
            BufferDescription bd = new BufferDescription();
            bd.SizeInBytes = Marshal.SizeOf(typeof(ShaderConstants));
            bd.Usage = ResourceUsage.Dynamic;
            bd.BindFlags = BindFlags.ConstantBuffer;
            bd.CpuAccessFlags = CpuAccessFlags.Write;
            bd.OptionFlags = ResourceOptionFlags.None;
            bd.StructureByteStride = 0;

            constantsBuffer = new Buffer(device, bd);
            constants = new ShaderConstants();

            //int t = Utils.GetBinResize(17);
        }

        public static void FreeResource()
        {
            EEEM.Dispose();
            constantsBuffer.Dispose();
            Vertexs.Dispose();
        }

        public static void DrawLens(DVector3 Position, Vector2 Size, Vector2 UVMin, Vector2 UVMax, Vector4 Color, TextureContainer Textur)
        {
            if (Textur == null) return;
            if (LastFreeIndex >= MassSize)
            {
                //resize
                MassSize = Utils.GetBinResize(LastFreeIndex + 1);
                System.Array.Resize(ref MasterRequest, MassSize);
                System.Array.Resize(ref SlaveRequest, MassSize);

                for (int i = LastFreeIndex; i < MassSize; i++)
                {
                    MasterRequest[i] = new Request();
                    SlaveRequest[i] = new Request();
                    ResizeVertex = true;
                }
            }

            MasterRequest[LastFreeIndex].Position = Position;
            MasterRequest[LastFreeIndex].Size = Size;
            MasterRequest[LastFreeIndex].UVMin = UVMin;
            MasterRequest[LastFreeIndex].UVMax = UVMax;
            MasterRequest[LastFreeIndex].Color = Color;
            MasterRequest[LastFreeIndex].Textur = Textur;

            LastFreeIndex++;
        }

        public static void BatchingDrawLens(Device device, EEDCamera Camera, ShaderResourceView DepthMap)
        {
            if (LastFreeIndex > 0)
            {
                if (ResizeVertex)
                {
                    Vertexs = new Buffer(device, BytesPerVertex * MassSize, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
                    //binding = new VertexBufferBinding(Vertexs, BytesPerVertex, 0);
                    ResizeVertex = false;
                }
                //sorting draw

                constants.FarPlane = new Vector4(Camera.farClip, 0, 0, 0);
                constants.VP = Matrix.Transpose(Camera.viewProjectionMatrix);
                var dataBox = ModelViewer.Program.device.ImmediateContext.MapSubresource(constantsBuffer, 0, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None);
                Utilities.Write(dataBox.DataPointer, ref constants);
                ModelViewer.Program.device.ImmediateContext.UnmapSubresource(constantsBuffer, 0);

                device.ImmediateContext.InputAssembler.InputLayout = EEEM.layout;
                device.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;

                EEEM.renderTechniques[0].GetPassByIndex(0).Apply(device.ImmediateContext);
                device.ImmediateContext.VertexShader.SetShaderResource(1, DepthMap);
                //Константный буффер передается шейдерам
                device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
                device.ImmediateContext.PixelShader.SetSampler(0, DrawHelper.SamplAnsWrapMip);

                SortDraw(ref MasterRequest, ref SlaveRequest, LastFreeIndex, device, Camera, DepthMap);
                LastFreeIndex = 0;
            }
        }

        private static void SortDraw(ref Request[] In, ref Request[] Out, int InCount, Device device, EEDCamera Camera, ShaderResourceView DepthMap)
        {
            int OutCount = 0;
            if (InCount > 0)
            {
                var vertices = new DataStream(BytesPerVertex * MassSize, true, true);
                int DrawVertexCout = 0;
                string FirstTexName = In[0].Textur.Name;

                for (int i = 0; i < InCount; i++)
                {
                    if (In[i].Textur.Name == FirstTexName)
                    {
                        //Go vertex
                        DrawVertexCout++;
                        vertices.Write(Conversion.ToVector3(In[i].Position - Camera.position));
                        vertices.Write(new Half2(In[i].Size.X, In[i].Size.Y));
                        vertices.Write(new Half4(In[i].UVMin.X, In[i].UVMin.Y, In[i].UVMax.X, In[i].UVMax.Y));
                        vertices.Write(new Half4(In[i].Color.X, In[i].Color.Y, In[i].Color.Z, In[i].Color.W));
                    }
                    else
                    {
                        //Go out
                        Out[OutCount] = In[i];
                        OutCount++;
                    }
                }
                vertices.Position = 0;

                DataStream stream;
                device.ImmediateContext.MapSubresource(Vertexs, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None, out stream);
                stream.Position = 0;
                stream.WriteRange(vertices.DataPointer, vertices.Length);
                device.ImmediateContext.UnmapSubresource(Vertexs, 0);
                stream.Dispose();
                vertices.Dispose();
                binding = new VertexBufferBinding(Vertexs, BytesPerVertex, 0);

                device.ImmediateContext.InputAssembler.SetVertexBuffers(0, binding);
                device.ImmediateContext.PixelShader.SetShaderResource(0, In[0].Textur.TextureResource);
                device.ImmediateContext.Draw(DrawVertexCout, 0);

                SortDraw(ref Out, ref In, OutCount, device, Camera, DepthMap);
            }
        }

        private struct ShaderConstants
        {
            public Matrix VP;
            public Vector4 FarPlane;
        }

        private struct Request
        {
            public DVector3 Position;
            public Vector2 Size;
            public Vector2 UVMin;
            public Vector2 UVMax;
            public Vector4 Color;
            public TextureContainer Textur;
        }
    }
}
